# 容器(Collections)
# Python附带一个模块，它包含许多容器数据类型，名字叫作collections。我们将讨论它的作用和用法。
# 我们将讨论的是：
# defaultdict
# Counter
# deque
# namedtuple
# enum.Enum (包含在Python 3.4以上)
from collections import defaultdict, Counter, deque, namedtuple


# 1. defaultdict
# 与dict类型不同，你不需要检查key是否存在，所以我们能这样做
colours = (
    ('Yasoob', 'Yellow'),
    ('Ali', 'Blue'),
    ('Arham', 'Green'),
    ('Ali', 'Black'),
    ('Yasoob', 'Red'),
    ('Ahmed', 'Silver'),
)
favourite_colors = defaultdict(list)
for name, color in colours:
    favourite_colors[name].append(color)

print(favourite_colors)


# 另一种重要的是例子就是：当你在一个字典中对一个键进行嵌套赋值时，如果这个键不存在，会触发keyError异常。
# defaultdict允许我们用一个聪明的方式绕过这个问题。 首先我分享一个使用dict触发KeyError的例子，然后提供一个使用defaultdict的解决方案
# 问题：
some_dict = {}
# some_dict['colours']['favourite'] = "yellow"
## 异常输出：KeyError: 'colours'

# 解决方案：
import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"
## 运行正常
# 你可以用json.dumps打印出some_dict，例如：
import json
print(json.dumps(some_dict))
## 输出: {"colours": {"favourite": "yellow"}}


# 2. Counter
# Counter是一个计数器，它可以帮助我们针对某项数据进行计数。比如它可以用来计算每个人喜欢多少种颜色：

from collections import Counter

colours = (
    ('Yasoob', 'Yellow'),
    ('Ali', 'Blue'),
    ('Arham', 'Green'),
    ('Ali', 'Black'),
    ('Yasoob', 'Red'),
    ('Ahmed', 'Silver'),
)

favs = Counter(name for name, colour in colours)
print(favs)

## 输出:
## Counter({
##     'Yasoob': 2,
##     'Ali': 2,
##     'Arham': 1,
##     'Ahmed': 1
##  })
# 我们也可以在利用它统计一个文件，例如：
# with open('filename', 'rb') as f:
#     line_count = Counter(f)
# print(line_count)


# 3. deque
# deque提供了一个双端队列，你可以从头/尾两端添加或删除元素
d = deque()
# 它的用法就像python的list，并且提供了类似的方法，例如：
d = deque()
d.append('1')
d.append('2')
d.append('3')
print(len(d))
## 输出: 3
print(d[0])
## 输出: '1'
print(d[-1])
## 输出: '3'
# 你可以从两端取出(pop)数据：

d = deque(range(5))
print(len(d))
## 输出: 5
d.popleft()
## 输出: 0
d.pop()
## 输出: 4
print(d)

## 输出: deque([1, 2, 3])
# 我们也可以限制这个列表的大小，当超出你设定的限制时，数据会从对队列另一端被挤出去(pop)。
# 最好的解释是给出一个例子：

d = deque(maxlen=30)
# 现在当你插入30条数据时，最左边一端的数据将从队列中删除。

# 你还可以从任一端扩展这个队列中的数据：

d = deque([1,2,3,4,5])
d.extendleft([0])
d.extend([6,7,8])
print(d)

## 输出: deque([0, 1, 2, 3, 4, 5, 6, 7, 8])

# 4. namedtuple
# 您可能已经熟悉元组。
# 一个元组是一个不可变的列表，你可以存储一个数据的序列，它和命名元组(namedtuples)非常像，但有几个关键的不同。
# 主要相似点是都不像列表，你不能修改元组中的数据。为了获取元组中的数据，你需要使用整数作为索引

# 那namedtuples是什么呢？它把元组变成一个针对简单任务的容器。你不必使用整数索引来访问一个namedtuples的数据。你可以像字典(dict)一样访问namedtuples，但namedtuples是不可变的

animal = namedtuple('Amimal', 'name, age type')
perry = animal(name='petter',age=18, type='dog')
print(perry)
print(perry.name)
# 我们再继续分析它。一个命名元组(namedtuple)有两个必需的参数。它们是元组名称和字段名称,属性值在namedtuple中是不可变的

# 你可以将一个命名元组转换为字典，方法如下：
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type="cat")
print(perry._asdict())


# 5.enum.Enum (Python 3.4+)
from enum import Enum
class Species(Enum):
    cat = 1
    dog = 2
    horse = 3
    aardvark = 4
    butterfly = 5
    owl = 6
    platypus = 7
    dragon = 8
    unicorn = 9
    # 依次类推

    # 但我们并不想关心同一物种的年龄，所以我们可以使用一个别名
    kitten = 1  # (译者注：幼小的猫咪)
    puppy = 2   # (译者注：幼小的狗狗)

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type=Species.cat)
drogon = Animal(name="Drogon", age=4, type=Species.dragon)
tom = Animal(name="Tom", age=75, type=Species.cat)
charlie = Animal(name="Charlie", age=2, type=Species.kitten)
# 现在，我们进行一些测试：
# >>> charlie.type == tom.type
# True
# >>> charlie.type
# <Species.cat: 1>
# 这样就没那么容易错误，我们必须更明确，而且我们应该只使用定义后的枚举类型。
#
# 有三种方法访问枚举数据，例如以下方法都可以获取到'cat'的值：

Species(1)
Species['cat']
Species.cat

